/**
 * @(#)Rower.java		
 * 
 * History:
 * 1.0	17 April 2012
 *		Created
 * 1.1  2 May 2012
 * 		Moved from upm.pb4agent.team1 to upm.pb4agent.team1.team
 * 		Extend AgBase1 instead of Agent
 * 2.0	12 May 2012
 * 		Introduced DockNumber
 * 		Behaviour is added based on the dock designation
 * 		Added behaviour to receive boat information from Watcher
 * 		Added behaviour to cancel subscription when registration is failed
 * 3.0	18 May 2012
 * 		Change SubscribeStartRace into SubscribeRefereeService
 * 		Change ReceiveStartRaceSignal into ReceiveRaceStartedSignal
 * 		Added behaviour to row: StartRowing
 * 3.1	21 May 2012
 * 		Applied single Watcher per team
 * 		Added behaviour to receive the reply of row request from the Watcher: ReceiveRealRowMessage & ReceiveDenyRowMessage
 * 3.2	27 May 2012
 * 		Added behaviour to receive information regarding rowers position from the Manager: ReceiveRowersPositionMessage
 * 		Added behaviour to receive start row signal from other Rower: ReceiveStartRowSignal
 * 		Added behaviour to receive stop row signal from other Rower: ReceiveStopRowSignal
 * 		Added behaviour to receive change seat message from other Rower: ReceiveChangeSeatMessage
 */
package upm.pb4agent.team1.team;

import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.util.HashMap;

import upm.pb4agent.ontology.AssignBoat;
import upm.pb4agent.ontology.Boat;
import upm.pb4agent.ontology.DenyRow;
import upm.pb4agent.ontology.EnteringDock;
import upm.pb4agent.ontology.LeavingDock;
import upm.pb4agent.ontology.PickSeat;
import upm.pb4agent.ontology.RaceFinished;
import upm.pb4agent.ontology.RaceStarted;
import upm.pb4agent.ontology.RealRow;
import upm.pb4agent.ontology.Row;
import upm.pb4agent.ontology.Seat;
import upm.pb4agent.ontology.SubscribeReferee;
import upm.pb4agent.team1.AgBase1;
import upm.pb4agent.team1.ontology.CancelSubscription;
import upm.pb4agent.team1.ontology.ChangeSeat;
import upm.pb4agent.team1.ontology.InformRowersPosition;
import upm.pb4agent.team1.ontology.StartRow;
import upm.pb4agent.team1.ontology.StopRow;
import upm.pb4agent.team1.team.Constant.Direction;
import upm.pb4agent.team1.team.Constant.DockNumber;
import upm.pb4agent.team1.team.Constant.Impulse;
import upm.pb4agent.team1.util.AgentUtil;

/**
 * This class handles the rower function.
 * 
 * @version 3.2
 * @author Bob Nicolaus
 */
public class Rower extends AgBase1 {

	private static final long serialVersionUID = 8653245752537015358L;

	private float strength = 0;
	private float resistance = 0;
	private float agility = 0;
	private float rowSeconds = 0;
	private float timeRow = 0;
	private float tiredness = 0;
	private float timeChangeSeat = 0;

	private DockNumber originalDock = null;
	private DockNumber currentDock = null;
	private Boat boat = null;
	private String seatID = null;
	private String firstSeatID = null;

	private AID watcherAID = null;
	private AID refereeAID = null;

	private boolean receivedBoatInformation = false;
	private boolean isRaceStarted = false;
	private boolean isRaceFinished = false;
	private boolean isRowAllowed = false;

	private int rowerImpulse = 0;
	private int circuitLength = 100;

	private HashMap<DockNumber, AID> rowersPosition = new HashMap<DockNumber, AID>();

	@Override
	protected void setup() {
		System.out.println(getLocalName() + ": Enters the system");

		Object[] args = getArguments();
		if (args != null) {
			strength = (Float) args[0];
			resistance = (Float) args[1];
			agility = (Float) args[2];
			originalDock = (DockNumber) args[3];
			rowSeconds = (Float) args[4];
			seatID = (String) args[5];
			firstSeatID = seatID;
			timeChangeSeat = (Float) args[6];
			circuitLength = (Integer) args[7];
		}

		currentDock = DockNumber.A;
		timeRow = rowSeconds - agility;
		tiredness = 0;

		// Subscribe to Referee
		try {
			subscribeRefereeService();
		} catch (CodecException ce) {
			ce.printStackTrace();
		} catch (OntologyException oe) {
			oe.printStackTrace();
		} catch (FIPAException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		addBehaviour(new ReceiveBoatInformation(this));
		addBehaviour(new ReceiveRowersPositionMessage(this));
		addBehaviour(new CancelRefereeServiceSubscription(this));
		addBehaviour(new ReceiveStopRowSignal(this));
		addBehaviour(new ReceiveStartRowSignal(this));
		addBehaviour(new ReceiveChangeSeatMessage(this));

		switch (originalDock) {
		case A:

			addBehaviour(new ReceiveRaceStartedSignal(this));
			break;
		case B:

			break;
		case C:

			break;
		case D:

			break;
		default:
			break;
		}

		addBehaviour(new ReceiveEnteringDockSignal(this));
		addBehaviour(new ReceiveLeavingDockSignal(this));
		addBehaviour(new StartRowing(this));
		addBehaviour(new ReceiveRealRowMessage(this));
		addBehaviour(new ReceiveDenyRowMessage(this));
		addBehaviour(new ReceiveRaceFinishedSignal(this));
	}

	/**
	 * Subscribes for Referee services
	 * 
	 * @throws FIPAException
	 * @throws CodecException
	 * @throws OntologyException
	 */
	private void subscribeRefereeService() throws FIPAException,
			CodecException, OntologyException, InterruptedException {
		if (refereeAID == null) {
			refereeAID = AgentUtil.searchAgentInDF(this,
					Constant.REFEREE_DF_TYPE);
		}

		Action action = new Action(refereeAID, new SubscribeReferee());

		sendMessage(ACLMessage.SUBSCRIBE, refereeAID, action,
				Constant.SUBSCRIBE_REFEREE_PROTOCOL);
		System.out.println(getLocalName()
				+ ": Send message to subscribe to referee");

		ACLMessage subscribeReplyMessage = blockingReceive(MessageTemplate.and(
				standardTemplate, MessageTemplate
						.MatchProtocol(Constant.SUBSCRIBE_REFEREE_PROTOCOL)));

		switch (subscribeReplyMessage.getPerformative()) {
		case ACLMessage.AGREE:
			System.out.println(getLocalName()
					+ ": Subscribed for Referee service");
			break;

		case ACLMessage.REFUSE:
			System.out.println(getLocalName() + ": Subscription was refused");
			break;

		default:
			break;
		}
	}

	/**
	 * Adjust impulse strength every time a rower enters a dock area
	 */
	private void adjustImpulseStrength() {
		String oarID;
		boolean twoOars = false;

		if (originalDock.equals(currentDock)) {

			if (seatID.equalsIgnoreCase("1") || seatID.equalsIgnoreCase("2")
					|| seatID.equalsIgnoreCase("3")) {
				oarID = Constant.oar1;
			} else {
				oarID = Constant.oar3;
			}

			if (currentDock == DockNumber.A || currentDock == DockNumber.B) {
				twoOars = true;
			} else {
				twoOars = false;
			}

			if (availableToReachDestiny(Impulse.Strong.ordinal(), tiredness,
					oarID, twoOars)) {
				rowerImpulse = Impulse.Strong.ordinal();
			} else if (availableToReachDestiny(Impulse.Medium.ordinal(),
					tiredness, oarID, twoOars)) {
				rowerImpulse = Impulse.Medium.ordinal();
			} else {
				rowerImpulse = Impulse.Weak.ordinal();
			}
		}
	}

	/**
	 * Analyse if a rower can reach the next dock with a certain impulse.
	 * 
	 * @param impulse
	 *            The impulse a rower intent to row with
	 * @param previousTiredness
	 *            The rower tiredness
	 * @param oarID
	 *            The rower oar ID
	 * @param twoOars
	 *            A flag to identify if the rower holds 2 oars
	 * @return
	 */
	private boolean availableToReachDestiny(int impulse,
			float previousTiredness, String oarID, boolean twoOars) {
		boolean result = true;
		int y = 0;
		int movement = 0;
		float quarterCircuitLength = circuitLength / 4;

		while (y < quarterCircuitLength) {
			movement = AgentUtil.calculateMovementY(impulse, strength,
					previousTiredness, oarID, Direction.Forward.ordinal());

			if (movement == 0) {
				result = false;
				break;
			}

			y = y + movement;

			previousTiredness = AgentUtil.calculateTotalTiredness(
					previousTiredness, impulse, resistance);

			// Repeat for two-oar holder
			if (twoOars == true) {
				movement = AgentUtil.calculateMovementY(impulse, strength,
						previousTiredness, oarID, Direction.Forward.ordinal());

				if (movement == 0) {
					result = false;
					break;
				}

				y = y + movement;

				previousTiredness = AgentUtil.calculateTotalTiredness(
						previousTiredness, impulse, resistance);
			}
		}

		return result;
	}

	/**
	 * Receives rowers position message from Manager.
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ReceiveRowersPositionMessage extends SimpleBehaviour {

		private static final long serialVersionUID = -7509842103299776320L;

		private boolean isDone = false;

		public ReceiveRowersPositionMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage rowersPositionMessage = receive(MessageTemplate
					.and(MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)),
							MessageTemplate
									.MatchProtocol(Constant.INFORM_ROWERS_POSITION_PROTOCOL)));

			if (rowersPositionMessage != null) {

				try {

					ContentElement contentElement = getContentManager()
							.extractContent(rowersPositionMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof InformRowersPosition) {
							InformRowersPosition informRowersPosition = (InformRowersPosition) concept;
							rowersPosition.put(DockNumber.A,
									(AID) informRowersPosition
											.getRowerAIDList().get(0));
							rowersPosition.put(DockNumber.B,
									(AID) informRowersPosition
											.getRowerAIDList().get(1));
							rowersPosition.put(DockNumber.C,
									(AID) informRowersPosition
											.getRowerAIDList().get(2));
							rowersPosition.put(DockNumber.D,
									(AID) informRowersPosition
											.getRowerAIDList().get(3));

							// Adjust impulse strength for the first rower
							adjustImpulseStrength();
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isDone == true;
		}

	}

	/**
	 * Cancels the subscription due to a failure in the team registration.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class CancelRefereeServiceSubscription extends SimpleBehaviour {

		private static final long serialVersionUID = -4182705458446040726L;

		public CancelRefereeServiceSubscription(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage cancelSignal = receive(MessageTemplate
					.and(MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)),
							MessageTemplate
									.MatchProtocol(Constant.CANCEL_SUBSCRIPTION_PROTOCOL)));

			if (cancelSignal != null) {

				try {

					ContentElement contentElement = getContentManager()
							.extractContent(cancelSignal);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof CancelSubscription) {
							AID refereeAID = AgentUtil.searchAgentInDF(myAgent,
									Constant.REFEREE_DF_TYPE);

							action = new Action(refereeAID,
									new SubscribeReferee());

							sendMessage(ACLMessage.CANCEL, refereeAID, action,
									Constant.SUBSCRIBE_REFEREE_PROTOCOL);
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				} catch (FIPAException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return receivedBoatInformation == true;
		}

	}

	/**
	 * Receives stop row signal from other Rower.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveStopRowSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -5414752322198297094L;

		public ReceiveStopRowSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage stopRowSignal = receive(MessageTemplate.and(
					MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate.MatchProtocol(Constant.STOP_ROW_PROTOCOL)));

			if (stopRowSignal != null) {

				try {

					ContentElement contentElement = getContentManager()
							.extractContent(stopRowSignal);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof StopRow) {

							System.out.println(getLocalName()
									+ " is not allowed to row!");
							isRowAllowed = false;

							// Adjust impulse strength
							adjustImpulseStrength();
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}

	}

	/**
	 * Receives start row signal from other Rower.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveStartRowSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -3835848905622152935L;

		public ReceiveStartRowSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage startRowSignal = receive(MessageTemplate.and(
					MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate.MatchProtocol(Constant.START_ROW_PROTOCOL)));

			if (startRowSignal != null) {

				try {

					ContentElement contentElement = getContentManager()
							.extractContent(startRowSignal);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof StartRow) {

							System.out.println(getLocalName()
									+ " is allowed to row again!");
							isRowAllowed = true;
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}

	}

	/**
	 * Receives change seat message from other Rower.
	 * 
	 * @author Morteza Nokhodian
	 * 
	 */
	class ReceiveChangeSeatMessage extends SimpleBehaviour {

		private static final long serialVersionUID = 662572300316976152L;

		public ReceiveChangeSeatMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage changeSeatMessage = receive(MessageTemplate.and(
					MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.REQUEST)),
					MessageTemplate
							.MatchProtocol(Constant.CHANGE_SEAT_PROTOCOL)));

			if (changeSeatMessage != null) {

				try {

					ContentElement contentElement = getContentManager()
							.extractContent(changeSeatMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof ChangeSeat) {
							ChangeSeat changeSeat = (ChangeSeat) concept;
							seatID = changeSeat.getSeatID();

							// Send message to boat
							Seat newSeat = new Seat();
							newSeat.setSeatID(seatID);
							PickSeat pickSeat = new PickSeat();
							pickSeat.setSeatID(newSeat);
							action = new Action(boat.getID(), pickSeat);
							sendMessage(ACLMessage.INFORM, boat.getID(),
									action, Constant.PICK_SEAT_PROTOCOL);

							Thread.sleep(((Float) timeChangeSeat).longValue());

							// Send reply message to the sender
							replyInternalMessage(changeSeatMessage,
									ACLMessage.AGREE,
									Constant.CHANGE_SEAT_PROTOCOL);
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}

	}

	/**
	 * Receives the boat information message from Watcher.
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ReceiveBoatInformation extends SimpleBehaviour {

		private static final long serialVersionUID = -6932436482523351023L;

		public ReceiveBoatInformation(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage boatInformation = receive(MessageTemplate.and(
					MessageTemplate.and(standardTemplate, MessageTemplate
							.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate
							.MatchProtocol(Constant.ASSIGN_BOAT_PROTOCOL)));

			if (boatInformation != null) {
				try {

					ContentElement contentElement = getContentManager()
							.extractContent(boatInformation);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof AssignBoat) {
							AssignBoat assignBoat = (AssignBoat) concept;

							watcherAID = boatInformation.getSender();
							boat = assignBoat.getBoat();

							if (originalDock == DockNumber.A) {
								// Send message to boat
								Seat newSeat = new Seat();
								newSeat.setSeatID(firstSeatID);
								PickSeat pickSeat = new PickSeat();
								pickSeat.setSeatID(newSeat);
								action = new Action(boat.getID(), pickSeat);
								sendMessage(ACLMessage.INFORM, boat.getID(),
										action, Constant.PICK_SEAT_PROTOCOL);
							}

							receivedBoatInformation = true;
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return receivedBoatInformation == true;
		}
	}

	/**
	 * Receives start race signal from Referee.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveRaceStartedSignal extends SimpleBehaviour {

		private static final long serialVersionUID = 3844912585144138107L;

		public ReceiveRaceStartedSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (receivedBoatInformation == true) {
				ACLMessage startRaceSignalMessage = receive(MessageTemplate
						.and(MessageTemplate.and(standardTemplate,
								MessageTemplate
										.MatchPerformative(ACLMessage.INFORM)),
								MessageTemplate
										.MatchProtocol(Constant.RACE_STARTED_PROTOCOL)));

				if (startRaceSignalMessage != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								startRaceSignalMessage);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof RaceStarted) {
								isRaceStarted = true;
								System.out
										.println(getLocalName()
												+ ": Alert message is received. Race is started");

								isRowAllowed = true;
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceStarted == true;
		}
	}

	/**
	 * Receives entering dock signal
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveEnteringDockSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -2731132770931238946L;

		public ReceiveEnteringDockSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (receivedBoatInformation == true) {
				ACLMessage enteringDockSignal = receive(MessageTemplate
						.and(MessageTemplate.and(standardTemplate,
								MessageTemplate
										.MatchPerformative(ACLMessage.INFORM)),
								MessageTemplate
										.MatchProtocol(Constant.ENTERING_DOCK_PROTOCOL)));

				if (enteringDockSignal != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								enteringDockSignal);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof EnteringDock) {
								EnteringDock enteringDock = (EnteringDock) concept;

								setCurrentDock(enteringDock.getDock().getName());

								if (enteringDock.getBoatAID().equals(
										boat.getID())) {

									// isRowAllowed = false;

									System.out.println(getLocalName()
											+ ": Entering dock "
											+ enteringDock.getDock().getName());

									if (enteringDock
											.getDock()
											.getName()
											.equalsIgnoreCase(
													originalDock.name())) {

										switch (originalDock) {
										case B:
											// Send message to first rower to
											// stop rowing
											stopRow(rowersPosition
													.get(DockNumber.A));

											// Choose seat and send message to
											// boat
											pickSeat(originalDock);

											// Adjust impulse strength
											adjustImpulseStrength();

											// Send message to first rower to
											// start rowing
											startRow(rowersPosition
													.get(DockNumber.A));

											isRowAllowed = true;
											break;
										case C:
											// Send message to first and second
											// rower to stop rowing
											stopRow(rowersPosition
													.get(DockNumber.A));
											stopRow(rowersPosition
													.get(DockNumber.B));

											// Choose seat and send message to
											// boat
											pickSeat(originalDock);

											// Adjust impulse strength
											adjustImpulseStrength();

											// Send message to first rower to
											// change seat
											changeSeat(originalDock,
													rowersPosition
															.get(DockNumber.A));

											// Send message to first and second
											// rower to start rowing
											startRow(rowersPosition
													.get(DockNumber.A));
											startRow(rowersPosition
													.get(DockNumber.B));

											isRowAllowed = true;
											break;
										case D:
											// Send message to first, second,
											// and third rower to stop rowing
											stopRow(rowersPosition
													.get(DockNumber.A));
											stopRow(rowersPosition
													.get(DockNumber.B));
											stopRow(rowersPosition
													.get(DockNumber.C));

											// Choose seat and send message to
											// boat
											pickSeat(originalDock);

											// Adjust impulse strength
											adjustImpulseStrength();

											// Send message to second rower to
											// change seat
											changeSeat(originalDock,
													rowersPosition
															.get(DockNumber.B));

											// Send message to first and second
											// rower to start rowing
											startRow(rowersPosition
													.get(DockNumber.A));
											startRow(rowersPosition
													.get(DockNumber.B));
											startRow(rowersPosition
													.get(DockNumber.C));

											isRowAllowed = true;
											break;
										default:
											break;
										}
									}
								}
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}

		/**
		 * Set current dock
		 * 
		 * @param dockName
		 */
		private void setCurrentDock(String dockName) {
			if (dockName.equalsIgnoreCase("A")) {
				currentDock = DockNumber.A;
			} else if (dockName.equalsIgnoreCase("B")) {
				currentDock = DockNumber.B;
			} else if (dockName.equalsIgnoreCase("C")) {
				currentDock = DockNumber.C;
			} else if (dockName.equalsIgnoreCase("D")) {
				currentDock = DockNumber.D;
			}
		}

		/**
		 * Send message to rower to stop row.
		 * 
		 * @param rowerAID
		 *            The AID of the receiver
		 * @throws CodecException
		 * @throws OntologyException
		 */
		private void stopRow(AID rowerAID) throws CodecException,
				OntologyException {
			Action action = new Action(rowerAID, new StopRow());
			sendInternalMessage(ACLMessage.INFORM, rowerAID, action,
					Constant.STOP_ROW_PROTOCOL);
		}

		/**
		 * Send message to rower to start row.
		 * 
		 * @param rowerAID
		 *            The AID of the receiver
		 * @throws CodecException
		 * @throws OntologyException
		 */
		private void startRow(AID rowerAID) throws CodecException,
				OntologyException {
			Action action = new Action(rowerAID, new StartRow());
			sendInternalMessage(ACLMessage.INFORM, rowerAID, action,
					Constant.START_ROW_PROTOCOL);
		}

		/**
		 * Choose a seat and inform Boat.
		 * 
		 * @param dock
		 *            Dock number
		 * @throws CodecException
		 * @throws OntologyException
		 */
		private void pickSeat(DockNumber dock) throws CodecException,
				OntologyException {

			switch (dock) {
			case B:
				if (firstSeatID.equalsIgnoreCase("2")) {
					seatID = "5";
				} else {
					seatID = "2";
				}

				break;
			case C:
				if (firstSeatID.equalsIgnoreCase("2")) {
					seatID = "1";
				} else {
					seatID = "4";
				}

				break;
			case D:
				if (firstSeatID.equalsIgnoreCase("2")) {
					seatID = "4";
				} else {
					seatID = "1";
				}

				break;
			default:
				break;
			}

			Seat newSeat = new Seat();
			newSeat.setSeatID(seatID);
			PickSeat pickSeat = new PickSeat();
			pickSeat.setSeatID(newSeat);
			Action action = new Action(boat.getID(), pickSeat);
			sendMessage(ACLMessage.INFORM, boat.getID(), action,
					Constant.PICK_SEAT_PROTOCOL);
		}

		/**
		 * Ask another rower to change seat.
		 * 
		 * @param dock
		 *            Dock number
		 * @param rowerAID
		 *            The rower that has to change seat
		 * @throws CodecException
		 * @throws OntologyException
		 */
		private void changeSeat(DockNumber dock, AID rowerAID)
				throws CodecException, OntologyException {

			ChangeSeat changeSeat = new ChangeSeat();
			switch (dock) {
			case C:
				if (firstSeatID.equalsIgnoreCase("2")) {
					changeSeat.setSeatID("3");
				} else {
					changeSeat.setSeatID("6");
				}

				break;
			case D:
				if (firstSeatID.equalsIgnoreCase("2")) {
					changeSeat.setSeatID("6");
				} else {
					changeSeat.setSeatID("3");
				}

				break;
			default:
				break;
			}

			Action action = new Action(rowerAID, changeSeat);
			sendInternalMessage(ACLMessage.REQUEST, rowerAID, action,
					Constant.CHANGE_SEAT_PROTOCOL);

			// Wait until rower finish moving
			blockingReceive(MessageTemplate.and(MessageTemplate.and(
					standardInternalTemplate,
					MessageTemplate.MatchPerformative(ACLMessage.AGREE)),
					MessageTemplate
							.MatchProtocol(Constant.CHANGE_SEAT_PROTOCOL)));
		}
	}

	/**
	 * Receives leaving dock signal
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveLeavingDockSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -7753008550982018612L;

		public ReceiveLeavingDockSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (receivedBoatInformation == true) {
				ACLMessage leavingDockSignal = receive(MessageTemplate.and(
						MessageTemplate.and(standardTemplate, MessageTemplate
								.MatchPerformative(ACLMessage.INFORM)),
						MessageTemplate
								.MatchProtocol(Constant.LEAVING_DOCK_PROTOCOL)));

				if (leavingDockSignal != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								leavingDockSignal);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof LeavingDock) {
								LeavingDock leavingDock = (LeavingDock) concept;

								if (leavingDock.getBoatAID() == boat.getID()) {

									System.out.println(getLocalName()
											+ ": Leaving dock "
											+ leavingDock.getDock());
									if (leavingDock.getDock().getName() == originalDock
											.name()) {

										switch (originalDock) {
										case B:

											break;
										case C:

											break;
										case D:

											break;
										default:
											break;
										}
									}
								}
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Receives race finished signal from Referee
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveRaceFinishedSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -6969207668891375109L;

		public ReceiveRaceFinishedSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage raceFinishedMessage = receive(MessageTemplate.and(
					MessageTemplate.and(standardTemplate, MessageTemplate
							.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate
							.MatchProtocol(Constant.RACE_FINISHED_PROTOCOL)));

			if (raceFinishedMessage != null) {
				ContentElement contentElement;
				try {

					contentElement = getContentManager().extractContent(
							raceFinishedMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof RaceFinished) {
							RaceFinished raceFinished = (RaceFinished) concept;

							if (raceFinished.getBoatAID().equals(boat.getID())) {

								isRaceFinished = true;
								isRowAllowed = false;
								System.out
										.println(getLocalName()
												+ ": Alert message is received. Race is finished");
							}
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Handles the rowing action strategy.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class StartRowing extends SimpleBehaviour {

		private static final long serialVersionUID = -7364987292521264902L;

		public StartRowing(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (isRowAllowed == true && isRaceFinished == false) {

				try {

					Row row = null;
					Action action = null;

					switch (Integer.valueOf(seatID)) {
					case 1:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						row.setOarID(Constant.oar1);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					case 2:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						// First oar
						row.setOarID(Constant.oar1);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						tiredness = AgentUtil.calculateTotalTiredness(
								tiredness, row.getStrength(), resistance);
						timeRow = timeRow + tiredness;

						// Second oar
						row.setOarID(Constant.oar2);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					case 3:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						row.setOarID(Constant.oar2);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					case 4:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						row.setOarID(Constant.oar3);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					case 5:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						// First oar
						row.setOarID(Constant.oar3);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						tiredness = AgentUtil.calculateTotalTiredness(
								tiredness, row.getStrength(), resistance);
						timeRow = timeRow + tiredness;

						// Second oar
						row.setOarID(Constant.oar4);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					case 6:
						row = new Row();
						row.setBoat(boat);
						row.setDirection(Direction.Forward.ordinal());
						row.setStrength(rowerImpulse);

						row.setOarID(Constant.oar4);
						action = new Action(watcherAID, row);
						sendMessage(ACLMessage.REQUEST, watcherAID, action,
								Constant.ROW_PROTOCOL);

						break;
					default:
						break;
					}

					tiredness = AgentUtil.calculateTotalTiredness(tiredness,
							row.getStrength(), resistance);
					timeRow = timeRow + tiredness;

					long sleepingTime = ((Double) Math.ceil(timeRow))
							.longValue() / 10;

					Thread.sleep(sleepingTime);
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Handles real row message sent by Watcher.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveRealRowMessage extends SimpleBehaviour {

		private static final long serialVersionUID = 8598978456906888697L;

		public ReceiveRealRowMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (isRaceStarted == true) {
				ACLMessage realRowMessage = receive(MessageTemplate.and(
						MessageTemplate.and(standardTemplate, MessageTemplate
								.MatchPerformative(ACLMessage.INFORM)),
						MessageTemplate
								.MatchProtocol(Constant.REAL_ROW_PROTOCOL)));

				if (realRowMessage != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								realRowMessage);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof RealRow) {
								// Do nothing
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Handles deny row message sent by Watcher.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveDenyRowMessage extends SimpleBehaviour {

		private static final long serialVersionUID = -2186091518055208286L;

		public ReceiveDenyRowMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (isRaceStarted == true) {
				ACLMessage denyRowMessage = receive(MessageTemplate.and(
						MessageTemplate.and(standardTemplate, MessageTemplate
								.MatchPerformative(ACLMessage.REFUSE)),
						MessageTemplate
								.MatchProtocol(Constant.DENY_ROW_PROTOCOL)));

				if (denyRowMessage != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								denyRowMessage);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof DenyRow) {
								// Do nothing
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}
}
